/**
 * jQuery Masonry v2.1.03
 * A dynamic layout plugin for jQuery
 * The flip-side of CSS Floats
 * http://masonry.desandro.com
 *
 * Licensed under the MIT license.
 * Copyright 2011 David DeSandro
 */

/*jshint browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true */
/*global jQuery: false */

(function( window, $, undefined ){

    'use strict';

    /*
     * smartresize: debounced resize event for jQuery
     *
     * latest version and complete README available on Github:
     * https://github.com/louisremi/jquery.smartresize.js
     *
     * Copyright 2011 @louis_remi
     * Licensed under the MIT license.
     */

    var $event = $.event,
        resizeTimeout;

    $event.special.smartresize = {
        setup: function() {
            $(this).bind( "resize", $event.special.smartresize.handler );
        },
        teardown: function() {
            $(this).unbind( "resize", $event.special.smartresize.handler );
        },
        handler: function( event, execAsap ) {
            // Save the context
            var context = this,
                args = arguments;

            // set correct event type
            event.type = "smartresize";

            if ( resizeTimeout ) { clearTimeout( resizeTimeout ); }
            resizeTimeout = setTimeout(function() {
                jQuery.event.handle.apply( context, args );
            }, execAsap === "execAsap"? 0 : 100 );
        }
    };

    $.fn.smartresize = function( fn ) {
        return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
    };



// ========================= Masonry ===============================


    // our "Widget" object constructor
    $.Mason = function( options, element ){
        this.element = $( element );

        this._create( options );
        this._init();
    };

    $.Mason.settings = {
        isResizable: true,
        isAnimated: false,
        animationOptions: {
            queue: false,
            duration: 500
        },
        gutterWidth: 0,
        isRTL: false,
        isFitWidth: false,
        containerStyle: {
            position: 'relative'
        }
    };

    $.Mason.prototype = {

        _filterFindBricks: function( $elems ) {
            var selector = this.options.itemSelector;
            // if there is a selector
            // filter/find appropriate item elements
            return !selector ? $elems : $elems.filter( selector ).add( $elems.find( selector ) );
        },

        _getBricks: function( $elems ) {
            var $bricks = this._filterFindBricks( $elems )
                .css({ position: 'absolute' })
                .addClass('masonry-brick');
            return $bricks;
        },

        // sets up widget
        _create : function( options ) {

            this.options = $.extend( true, {}, $.Mason.settings, options );
            this.styleQueue = [];

            // get original styles in case we re-apply them in .destroy()
            var elemStyle = this.element[0].style;
            this.originalStyle = {
                // get height
                height: elemStyle.height || ''
            };
            // get other styles that will be overwritten
            var containerStyle = this.options.containerStyle;
            for ( var prop in containerStyle ) {
                this.originalStyle[ prop ] = elemStyle[ prop ] || '';
            }

            this.element.css( containerStyle );

            this.horizontalDirection = this.options.isRTL ? 'right' : 'left';

            this.offset = {
                x: parseInt( this.element.css( 'padding-' + this.horizontalDirection ), 10 ),
                y: parseInt( this.element.css( 'padding-top' ), 10 )
            };
            //console.log(this.offset.y);
            this.isFluid = this.options.columnWidth && typeof this.options.columnWidth === 'function';

            // add masonry class first time around
            var instance = this;
            setTimeout( function() {
                instance.element.addClass('masonry');
            }, 0 );

            // bind resize method
            if ( this.options.isResizable ) {
                $(window).bind( 'smartresize.masonry', function() {
                    instance.resize();
                });
            }


            // need to get bricks
            this.reloadItems();

        },

        // _init fires when instance is first created
        // and when instance is triggered again -> $el.masonry();
        _init : function( callback ) {
            this._getColumns();
            this._reLayout( callback );
        },

        option: function( key, value ){
            // set options AFTER initialization:
            // signature: $('#foo').bar({ cool:false });
            if ( $.isPlainObject( key ) ){
                this.options = $.extend(true, this.options, key);
            }
        },

        // ====================== General Layout ======================

        // used on collection of atoms (should be filtered, and sorted before )
        // accepts atoms-to-be-laid-out to start with
        layout : function( $bricks, callback ) {

            // place each brick
            for (var i=0, len = $bricks.length; i < len; i++) {
                this._placeBrick( $bricks[i] );
            }

            // set the size of the container
            var containerSize = {};
            containerSize.height = Math.max.apply( Math, this.colYs );
            if ( this.options.isFitWidth ) {
                var unusedCols = 0;
                i = this.cols;
                // count unused columns
                while ( --i ) {
                    if ( this.colYs[i] !== 0 ) {
                        break;
                    }
                    unusedCols++;
                }
                // fit container to columns that have been used;
                containerSize.width = (this.cols - unusedCols) * this.columnWidth - this.options.gutterWidth;
            }
            this.styleQueue.push({ $el: this.element, style: containerSize });

            // are we animating the layout arrangement?
            // use plugin-ish syntax for css or animate
            var styleFn = !this.isLaidOut ? 'css' : (
                    this.options.isAnimated ? 'animate' : 'css'
                ),
                animOpts = this.options.animationOptions;

            // process styleQueue
            var obj;
            for (i=0, len = this.styleQueue.length; i < len; i++) {
                obj = this.styleQueue[i];
                obj.$el[ styleFn ]( obj.style, animOpts );
            }

            // clear out queue for next time
            this.styleQueue = [];

            // provide $elems as context for the callback
            if ( callback ) {
                callback.call( $bricks );
            }

            this.isLaidOut = true;
        },

        // calculates number of columns
        // i.e. this.columnWidth = 200
        _getColumns : function() {
            var container = this.options.isFitWidth ? this.element.parent() : this.element,
                containerWidth = container.width();

            // use fluid columnWidth function if there
            this.columnWidth = this.isFluid ? this.options.columnWidth( containerWidth ) :
                // if not, how about the explicitly set option?
                this.options.columnWidth ||
                // or use the size of the first item
                this.$bricks.outerWidth(true) ||
                // if there's no items, use size of container
                containerWidth;

            this.columnWidth += this.options.gutterWidth;

            this.cols = Math.floor( ( containerWidth + this.options.gutterWidth ) / this.columnWidth );
            this.cols = Math.max( this.cols, 1 );

        },

        // layout logic
        _placeBrick: function( brick ) {
            var $brick = $(brick),
                colSpan, groupCount, groupY, groupColY, j;

            //how many columns does this brick span
            colSpan = Math.ceil( $brick.outerWidth(true) /
                ( this.columnWidth + this.options.gutterWidth ) );
            colSpan = Math.min( colSpan, this.cols );

            if ( colSpan === 1 ) {
                // if brick spans only one column, just like singleMode
                groupY = this.colYs;
            } else {
                // brick spans more than one column
                // how many different places could this brick fit horizontally
                groupCount = this.cols + 1 - colSpan;
                groupY = [];

                // for each group potential horizontal position
                for ( j=0; j < groupCount; j++ ) {
                    // make an array of colY values for that one group
                    groupColY = this.colYs.slice( j, j+colSpan );
                    // and get the max value of the array
                    groupY[j] = Math.max.apply( Math, groupColY );
                }

            }

            // get the minimum Y value from the columns
            var minimumY = Math.min.apply( Math, groupY ),
                shortCol = 0;
            //console.log(Math);
            // Find index of short column, the first from the left
            for (var i=0, len = groupY.length; i < len; i++) {
                if ( groupY[i] === minimumY ) {
                    shortCol = i;
                    break;
                }
            }

            // position the brick
            var position = {
                top: minimumY + this.offset.y
            };
            // position.left or position.right
            position[ this.horizontalDirection ] = this.columnWidth * shortCol + this.offset.x;

            this.styleQueue.push({ $el: $brick, style: position });

            // apply setHeight to necessary columns
            var setHeight = minimumY + $brick.outerHeight(true),
                setSpan = this.cols + 1 - len;
            for ( i=0; i < setSpan; i++ ) {
                this.colYs[ shortCol + i ] = setHeight;
            }

        },


        resize: function() {
            var prevColCount = this.cols;
            // get updated colCount
            this._getColumns();
            if ( this.isFluid || this.cols !== prevColCount ) {
                // if column count has changed, trigger new layout
                this._reLayout();
            }
        },


        _reLayout : function( callback ) {
            // reset columns
            var i = this.cols;
            this.colYs = [];
            while (i--) {
                this.colYs.push( 0 );
            }
            // apply layout logic to all bricks
            this.layout( this.$bricks, callback );
        },

        // ====================== Convenience methods ======================

        // goes through all children again and gets bricks in proper order
        reloadItems : function() {
            this.$bricks = this._getBricks( this.element.children() );
        },


        reload : function( callback ) {
            this.reloadItems();
            this._init( callback );
        },


        // convienence method for working with Infinite Scroll
        appended : function( $content, isAnimatedFromBottom, callback ) {
            if ( isAnimatedFromBottom ) {
                // set new stuff to the bottom
                this._filterFindBricks( $content ).css({ top: this.element.height() });
                var instance = this;
                setTimeout( function(){
                    instance._appended( $content, callback );
                }, 1 );
            } else {
                this._appended( $content, callback );
            }
        },

        _appended : function( $content, callback ) {
            var $newBricks = this._getBricks( $content );
            // add new bricks to brick pool
            this.$bricks = this.$bricks.add( $newBricks );
            this.layout( $newBricks, callback );
        },

        // removes elements from Masonry widget
        remove : function( $content ) {
            this.$bricks = this.$bricks.not( $content );
            $content.remove();
        },

        // destroys widget, returns elements and container back (close) to original style
        destroy : function() {

            this.$bricks
                .removeClass('masonry-brick')
                .each(function(){
                    this.style.position = '';
                    this.style.top = '';
                    this.style.left = '';
                });

            // re-apply saved container styles
            var elemStyle = this.element[0].style;
            for ( var prop in this.originalStyle ) {
                elemStyle[ prop ] = this.originalStyle[ prop ];
            }

            this.element
                .unbind('.masonry')
                .removeClass('masonry')
                .removeData('masonry');

            $(window).unbind('.masonry');

        }

    };


    // ======================= imagesLoaded Plugin ===============================
    /*!
     * jQuery imagesLoaded plugin v1.1.0
     * http://github.com/desandro/imagesloaded
     *
     * MIT License. by Paul Irish et al.
     */


    // $('#my-container').imagesLoaded(myFunction)
    // or
    // $('img').imagesLoaded(myFunction)

    // execute a callback when all images have loaded.
    // needed because .load() doesn't work on cached images

    // callback function gets image collection as argument
    //  `this` is the container

    $.fn.imagesLoaded = function( callback ) {
        var $this = this,
            $images = $this.find('img').add( $this.filter('img') ),
            len = $images.length,
            blank = '',
            loaded = [];

        function triggerCallback() {
            callback.call( $this, $images );
        }

        function imgLoaded( event ) {
            var img = event.target;
            if ( img.src !== blank && $.inArray( img, loaded ) === -1 ){
                loaded.push( img );
                if ( --len <= 0 ){
                    setTimeout( triggerCallback );
                    $images.unbind( '.imagesLoaded', imgLoaded );
                }
            }
        }

        // if no images, trigger immediately
        if ( !len ) {
            triggerCallback();
        }

        $images.bind( 'load.imagesLoaded error.imagesLoaded',  imgLoaded ).each( function() {
            // cached images don't fire load sometimes, so we reset src.
            var src = this.src;
            // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
            // data uri bypasses webkit log warning (thx doug jones)
            this.src = blank;
            this.src = src;
        });

        return $this;
    };


    // helper function for logging errors
    // $.error breaks jQuery chaining
    var logError = function( message ) {
        if ( window.console ) {
            window.console.error( message );
        }
    };

    // =======================  Plugin bridge  ===============================
    // leverages data method to either create or return $.Mason constructor
    // A bit from jQuery UI
    //   https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
    // A bit from jcarousel
    //   https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js

    $.fn.masonry = function( options ) {
        if ( typeof options === 'string' ) {
            // call method
            var args = Array.prototype.slice.call( arguments, 1 );

            this.each(function(){
                var instance = $.data( this, 'masonry' );
                if ( !instance ) {
                    logError( "cannot call methods on masonry prior to initialization; " +
                        "attempted to call method '" + options + "'" );
                    return;
                }
                if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
                    logError( "no such method '" + options + "' for masonry instance" );
                    return;
                }
                // apply method
                instance[ options ].apply( instance, args );
            });
        } else {
            this.each(function() {
                var instance = $.data( this, 'masonry' );
                if ( instance ) {
                    // apply options & init
                    instance.option( options || {} );
                    instance._init();
                } else {
                    // initialize new instance
                    $.data( this, 'masonry', new $.Mason( options, this ) );
                }
            });
        }
        return this;
    };

})( window, jQuery );